Imports System
Imports System.ComponentModel
Imports System.Text
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.HtmlControls

<DefaultProperty("Text"), ToolboxData("<{0}:WebChat runat=server></{0}:WebChat>")> _
Public Class WebChat
    Inherits WebControl
    Implements System.Web.UI.ICallbackEventHandler, INamingContainer

    'private variables
    Private _Messages As System.Collections.Generic.Queue(Of ChatMessage)
    Private _LastMsgID As Long = 0
    Private _pnl As Panel = New Panel
    Private _txt As TextBox = New TextBox

    'constructor
    Public Sub New()
        'set default size
        MyBase.Width = New Unit(350, UnitType.Pixel)
        MyBase.Height = New Unit(200, UnitType.Pixel)
    End Sub

#Region " Public Properties "

    ''' <summary>
    ''' The number of chat messages will be cached in application state
    ''' </summary>
    ''' <remarks></remarks>
    Private _HistoryCapacity As Integer = 10
    <Bindable(True), Category("Behavior"), DefaultValue(10), Localizable(True), Description("The number of chat messages will be cached in application state")> _
    Public Property HistoryCapacity() As Integer
        Get
            Return _HistoryCapacity
        End Get
        Set(ByVal value As Integer)
            If value < 1 Then value = 1
            _HistoryCapacity = value
        End Set
    End Property

    ''' <summary>
    ''' Sets or gets whether this control should recieve focus upon page load
    ''' </summary>
    ''' <remarks></remarks>
    Private _GetFocus As Boolean = True
    <Bindable(True), Category("Behavior"), DefaultValue(True), Localizable(True), Description("Sets or gets whether this control should receive focus upon page load")> _
    Public Property InitialFocus() As Boolean
        Get
            Return _GetFocus
        End Get
        Set(ByVal value As Boolean)
            _GetFocus = value
        End Set
    End Property

    ''' <summary>
    ''' The number of seconds between chat update requests
    ''' </summary>
    ''' <remarks></remarks>
    Private _CallBackInterval As Byte = 2
    <Bindable(True), Category("Behavior"), DefaultValue(GetType(Byte), "2"), Localizable(True), Description("The number of seconds between chat update requests")> _
    Public Property CallBackInterval() As Byte
        Get
            Return _CallBackInterval
        End Get
        Set(ByVal value As Byte)
            If value < 1 Then value = 1
            _CallBackInterval = value
        End Set
    End Property

    ''' <summary>
    ''' Sets or gets the name of the user that's chatting in this instance of the control
    ''' </summary>
    <Bindable(True), Category("Appearance"), DefaultValue("Anonymous"), Localizable(True), Browsable(False), Description("Sets or gets the name of the user that's chatting in this instance of the control")> _
    Property UserName() As String
        Get
            Dim s As String = CStr(ViewState("UserName"))
            If s Is Nothing Then
                Return "Anonymous"
            Else
                Return s
            End If
        End Get

        Set(ByVal Value As String)
            ViewState("UserName") = Value
        End Set
    End Property

    ''' <summary>
    ''' Sets or gets the the conversation with which this instance of the control is associated
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks>Some web sites may decide to have multiple chat rooms, each with different topics</remarks>
    <Bindable(True), Category("Behavior"), DefaultValue("general"), Localizable(True), Description("Sets or gets the the conversation topic with which this instance of the control is associated")> _
    Property ChatTopic() As String
        Get
            Dim s As String = CStr(ViewState("ChatTopic"))
            If s Is Nothing Then
                Return "general"
            Else
                Return s
            End If
        End Get

        Set(ByVal Value As String)
            ViewState("ChatTopic") = Value
        End Set
    End Property

#End Region


    Protected Overrides Sub RenderContents(ByVal output As HtmlTextWriter)

        'create the containing table
        Dim tbl As Table = New Table
        tbl.Height = Me.Height
        tbl.Width = Me.Width
        tbl.ToolTip = Me.ToolTip
        tbl.Style.Add("overflow", "scroll")
        tbl.Style.Add("position", "absolute")
        Dim td As TableCell = New TableCell
        td.ColumnSpan = 2
        td.Height = New Unit(95, UnitType.Percentage)
        Dim tr As TableRow = New TableRow
        tr.Cells.Add(td)
        tbl.Rows.Add(tr)

        'create the message display panel
        _pnl.Width = New Unit(99, UnitType.Percentage)
        _pnl.Height = New Unit(100, UnitType.Percentage)
        _pnl.BorderStyle = WebControls.BorderStyle.Solid
        _pnl.BorderWidth = New Unit(1, UnitType.Pixel)
        _pnl.Style.Add(HtmlTextWriterStyle.Overflow, "scroll")
        td.Controls.Add(_pnl)

        'create a new table row for textbox & button
        tr = New TableRow
        tbl.Rows.Add(tr)
        td = New TableCell
        td.Width = New Unit(95, UnitType.Percentage)
        tr.Cells.Add(td)

        'create the button 
        Dim btn As Button = New Button
        btn.ID = Me.ID & "_button"
        btn.UseSubmitBehavior = False
        btn.Text = "Send"

        'create the textbox
        _txt.Width = New Unit(99, UnitType.Percentage)
        _txt.BorderColor = Drawing.Color.Black
        _txt.BorderWidth = New Unit(1, UnitType.Pixel)
        _txt.Attributes("autocomplete") = "off"
        _txt.Attributes("onkeydown") = "if ((event.keyCode == 13)) {document.getElementById('" _
          & btn.ClientID & "').click();return false;} else return true;"
        _txt.Attributes("onkeypress") = "if (event.keyCode==124 || event.keyCode==126) event.keyCode=0;" 'prevent reserved (delimiter) characters from being entered
        td.Controls.Add(_txt)

        'create the final table cell
        td = New TableCell
        td.Controls.Add(btn)
        tr.Cells.Add(td)

        'Attach button's client event to the JavaScript functions
        btn.Attributes("OnClick") = "DoChatCallBack(document.getElementById('" & _txt.ClientID & "'));"

        tbl.RenderControl(output)
    End Sub

    Private Sub WebChat_Load(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles Me.Load
        _pnl.ID = Me.ID & "_chatpanel"
        _txt.ID = Me.ID & "_chattextbox"

        If Me.Visible Then
            'Retrieve the JavaScript functions 
            'that will recieve the result
            Dim sCallBack As String = _
                My.Resources.Callback.Replace("WebChat1", Me.ID)
            If Me.CallBackInterval <> 2 Then
                sCallBack = sCallBack.Replace("2000", _
                  (Me.CallBackInterval * 1000).ToString())
            End If
            Page.ClientScript.RegisterClientScriptBlock(Me.GetType, _
                "CalledBack", sCallBack, True)

            'Now generate the function that initiates 
            'the client side callback
            Dim sb As New StringBuilder()
            sb.Append(System.Environment.NewLine)
            sb.Append("function DoChatCallBack(txt)")
            sb.Append(System.Environment.NewLine)
            sb.Append("{")
            sb.Append("var msg = ''; if (txt != null) msg=txt.value; ")
            sb.Append(System.Environment.NewLine)
            sb.Append(Page.ClientScript.GetCallbackEventReference(Me, _
                "WebChatMaxMsgID + '||' + msg", _
                "CalledBack", "ErrCalledBack"))
            sb.Append(";")
            sb.Append(System.Environment.NewLine)
            sb.Append("if (txt != null) txt.value='';")
            sb.Append(System.Environment.NewLine)
            sb.Append("}")
            Dim sDoCallBack As String = sb.ToString()
            Page.ClientScript.RegisterClientScriptBlock(Me.GetType, _
                "DoChatCallBack", sDoCallBack, True)

            'set initial focus (if configured to do so)
            If Me.InitialFocus AndAlso Me.Enabled Then
                Page.ClientScript.RegisterStartupScript(Me.GetType, _
                "ChatFocus", "document.getElementById('" _
                & _txt.ClientID & "').focus();", True)
            End If

            'emit JavaScript function call that initializes 
            'the control and joins the user into the conversation
            Page.ClientScript.RegisterStartupScript(Me.GetType, _
                "ChatEnter", "ChatEnter();", True)
        End If
    End Sub


#Region " Callback (AJAX) Code "
    Public Event Chatter(ByRef ChatText As String)

    Public Sub RaiseCallbackEvent(ByVal _
    eventArgument As String) Implements _
    System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent
        Debug.WriteLine("Callback " & Date.Now.ToLongTimeString)

        'extract the last message id that was received by the client
        'so that we can pass back only the new messages
        Dim aryArgs As String() = eventArgument.Split("||".ToCharArray())
        _LastMsgID = CType(aryArgs(0), Long)
        eventArgument = aryArgs(2).Trim

        Dim WebChatTopic As String = "WebChat_Conversation_" & Me.ChatTopic

        'Raise Chatter event if new message was sent
        If eventArgument.Length > 0 Then
            RaiseEvent Chatter(eventArgument)
        End If

        'Massage the message cosmetically
        Dim ModifiedMsgText As String = "<b><i>" & _
            Me.UserName & "</i></b>: " & eventArgument

        'Does the conversation need instantiation?
        If Context.Application(WebChatTopic) Is Nothing Then
            'no existing queue found so create a new queue
            Debug.WriteLine("Creating new queue")
            _Messages = New System.Collections.Generic.Queue(Of ChatMessage)
            If eventArgument.Trim.Length > 0 Then
                Dim Msg As New _
                    ChatMessage(1, Date.Now, _
                    ModifiedMsgText, Me.UserName)
                _Messages.Enqueue(Msg)
            End If
            Context.Application.Lock()
            Context.Application(WebChatTopic) = _Messages
            Context.Application(WebChatTopic & "_MAX") = 1
            Context.Application.UnLock()
        Else 'existing Queue found, so use it
            Context.Application.Lock()
            _Messages = CType(Context.Application(WebChatTopic), _
                System.Collections.Generic.Queue(Of ChatMessage))
            If eventArgument.Trim.Length > 0 Then
                Dim Max As Long = CType(Context.Application(WebChatTopic & "_MAX"), Long)
                Max += 1
                Dim Msg As New ChatMessage(Max, Date.Now, ModifiedMsgText, Me.UserName)

                'keep track of the max message id
                Context.Application(WebChatTopic & "_MAX") = Max

                'place new message on the stack
                _Messages.Enqueue(Msg)

                'purge stale messages
                If _Messages.Count > _HistoryCapacity Then
                    Msg = _Messages.Dequeue()
                End If

            End If

            'put the queue back into application state
            Context.Application(WebChatTopic) = _Messages
            Context.Application.UnLock()
        End If
    End Sub

    Public Function GetCallbackResult() As String Implements _
        System.Web.UI.ICallbackEventHandler.GetCallbackResult
        Dim retval As String = ""

        Dim sb As New StringBuilder
        For Each Msg As ChatMessage In _Messages
            If Msg.MessageID > _LastMsgID Then
                'Build the return string 
                'containing recent chat messages
                sb.Append(Msg.MessageID.ToString())
                sb.Append("||") 'column delimiter
                sb.Append(Msg.MessageText)
                'sb.Append("||") 'column delimiter
                'sb.Append(Msg.MessageDateTime.ToString)
                'sb.Append("||") 'column delimiter
                'sb.Append(Msg.MessageSender)
                sb.Append("~~") 'row delimeter
            End If
        Next
        retval = sb.ToString()
        Return retval
    End Function

#End Region

End Class
